﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "GbmpCommandBase.h"
#include "GbmpUiPlatformCommandIds.h"
#include "GbmpUiPlatformCommandRegister.h"
#include "CmdStatusUtils.h"
#include "IModelView.h"
#include "UiCommonDialog.h"
#include "ActionDrawStraightElementInput.h"
#include "ActionDrawStraightElementCreator.h"
#include "IUiView.h"
#include "IDocument.h"
#include "IUserTransaction.h"
#include "GbmpModelViewUtil.h"
#include "Vector3dUtils.h"
#include "IGraphicsElementShape.h"
#include "IGraphicsNodeGroup.h"
#include "ITemporaryDimensionContext.h"
#include "ITemporaryDimensionManager.h"
#include "IGbmpSectionView.h"
#include "ISectionViewSymbolNew.h"
#include "IUiDocument.h"
#include "ISelection.h"
#include "IGraphicsNodeReference.h"
#include "GbmpGraphicsNodeUtils.h"
#include "IHighlights.h"
#include "LevelUtils.h"
#include "UiDocumentViewUtils.h"

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

using namespace gcmp;

#define BASE_NAME_SECTION_VIEW       GBMP_TR(L"剖面")

//////////////////////////////////////////////////////////////////////////
class CmdCreateSectionViewNew : public GbmpCommandBase
{
public:
    CmdCreateSectionViewNew();

    virtual bool IsEnabled() const override;
    virtual bool IsVisible() const override;

    virtual OwnerPtr<IAction> ExecuteCommand(const CommandParameters& cmdParams) override;

private:
    const std::wstring OnActionPromptMsg(DrawLineStatus drawLineStatus);
    OwnerPtr<IGraphicsNodeGroup> OnLineUpdate(IUiView* pCurrentView, const Vector3d& startPt, const Vector3d& endPt);
    ElementId OnLineFinished(IUiView* pCurrentView, const Vector3d& startPt, const Vector3d& endPt, const ISnap* pStartSnap, const ISnap* pEndSnap);

};

//////////////////////////////////////////////////////////////////////////
REGISTER_GM_COMMAND(CmdCreateSectionViewNew);

CmdCreateSectionViewNew::CmdCreateSectionViewNew()
    : GbmpCommandBase(ID_CMD_GBMP_CREATE_SECTION_VIEW)
{
}

bool CmdCreateSectionViewNew::IsEnabled() const
{
    CMD_ONLY_ENABLE_IN_PROJECT_ENVIRONMENT
}

bool CmdCreateSectionViewNew::IsVisible() const
{
    CMD_ONLY_VISIBLE_IN_PROJECT_ENVIRONMENT
}

OwnerPtr<IAction> CmdCreateSectionViewNew::ExecuteCommand(const CommandParameters& cmdParams)
{
    // 配置Action
    ActionDrawStraightElementInput actionInput;
    actionInput.CommandId = GetCommandId();
    actionInput.DrawLineMode = DrawLineMode::Only_One;
    actionInput.PromptMessageCallback = actionInput.CreateDefinePomptMessageCallback(&CmdCreateSectionViewNew::OnActionPromptMsg, this);
    actionInput.LineUpdateCallback = actionInput.CreateOnLineUpdateCallback(&CmdCreateSectionViewNew::OnLineUpdate, this);
    actionInput.LineFinishedCallback = actionInput.CreateOnLineFinishedCallback(&CmdCreateSectionViewNew::OnLineFinished, this);

    return ActionDrawStraightElementCreator::Create(actionInput);
}

const std::wstring CmdCreateSectionViewNew::OnActionPromptMsg(DrawLineStatus drawLineStatus)
{
    std::wstring promptMsg;
    switch (drawLineStatus)
    {
    case DrawLineStatus::Getting_StartPoint:
        promptMsg = GBMP_TR(L"请输入剖面起始点");
        break;
    case DrawLineStatus::Getting_EndPoint:
        promptMsg = GBMP_TR(L"请输入剖面终止点");
        break;
    default:
        break;
    }
    return promptMsg;
}

namespace
{
    // 根据当前视图的观察方向和剖面符号起终点位置，计算剖面视图原点及三个方向
    void GetSectionViewDirection(const Vector3d& TempViewDir, const Vector3d& startPt, const Vector3d& endPt,
        Vector3d& sectionViewPos, Vector3d& sectionViewDir, Vector3d& sectionViewRightDir, Vector3d& sectionViewUpDir)
    {
        DBG_WARN_AND_RETURN_VOID_UNLESS(startPt != endPt, L"起终点不能相同",L"GDMPLab",L"2024-03-30");
        DBG_WARN_AND_RETURN_VOID_UNLESS(!TempViewDir.IsZero(), L"当前视图方向不能为0",L"GDMPLab",L"2024-03-30");
        Vector3d lineDir = endPt - startPt;
        sectionViewDir = TempViewDir.Cross(lineDir);
        sectionViewDir.Normalize();
        double drawLineLength = lineDir.Normalize();   // 起点到终点的距离
        sectionViewPos = (startPt + endPt) / 2.0;
        Vector3d zVec = Vector3d::UnitZ;
        if (Vector3dUtils::IsParallel(sectionViewDir, zVec))
        {
            sectionViewRightDir = lineDir;
            sectionViewUpDir = sectionViewRightDir.Cross(sectionViewDir);
            sectionViewUpDir.Normalize();
        }
        else
        {
            sectionViewUpDir = zVec - sectionViewDir * (zVec * sectionViewDir);
            sectionViewUpDir.Normalize();
            sectionViewRightDir = sectionViewDir.Cross(sectionViewUpDir);
        }
    }
}

OwnerPtr<IGraphicsNodeGroup> CmdCreateSectionViewNew::OnLineUpdate(IUiView* pCurrentView, const Vector3d& startPt, const Vector3d& endPt)
{
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(startPt != endPt, L"起终点不能相同",L"GDMPLab",L"2024-03-30");
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pCurrentView, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
    IModelView* pModelView = pCurrentView->GetModelView();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pModelView, L"pModelView为空",L"GDMPLab",L"2024-03-30");
    IDocument* pDoc = UiDocumentViewUtils::GetCurrentDocument();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pDoc, L"pDoc为空",L"GDMPLab",L"2024-03-30");

    Coordinate3d coordWorkPlane = GbmpModelViewUtil::GetWorkPlaneCoordinate(pModelView);

    OwnerPtr<IGraphicsElementShape> opTempGRep = IGraphicsElementShape::Create(GraphicsRenderLayer::TransientObject);
    OwnerPtr<IGraphicsNodeGroup> opTempGroup = GbmpGraphicsNodeUtils::TransferGRepToGGroup(TransferOwnership(opTempGRep));

    // 创建临时剖面标头符号
    GbmpSectionViewInput input;
    {
        std::wstring viewName;
        bool bOk = GbmpModelViewUtil::GetNextModelViewName(pDoc, SectionViewType, BASE_NAME_SECTION_VIEW, L" ", viewName);
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(bOk, L"GetNextModelViewName失败",L"GDMPLab",L"2024-03-30");

        Vector3d origin, viewDirection, upDirection, rightDirection;
        GetSectionViewDirection(coordWorkPlane.Z(), startPt, endPt, origin, viewDirection, rightDirection, upDirection);
        Vector3d lineDir = endPt - startPt;
        double drawLineLength = lineDir.Normalize();

        input.Name = viewName;
        input.Origin = origin;
        input.ViewDirection = viewDirection;
        input.RightDirection = rightDirection;
        input.UpDirection = upDirection;
        if (Vector3dUtils::IsEqual(rightDirection, lineDir))   // 如果绘制的方向就是视图右方向，说明drawLineLength就是剖面的宽度
        {
            input.RectViewportWidth = drawLineLength;
            input.RectViewportHeight = 30000;
        }
        else        // 如果绘制的方向不是视图右方向，此时drawLineWidth是剖面的高度
        {
            input.RectViewportWidth = 30000;
            input.RectViewportHeight = drawLineLength;
        }
        input.SymbolLeftPtOffset = input.RectViewportWidth/2.0;
        input.SymbolRightPtOffset = input.SymbolLeftPtOffset;
        input.SymbolUpPtOffset = input.RectViewportHeight / 2;
        input.SymbolDownPtOffset = input.SymbolUpPtOffset;
        input.Document = pDoc;
    }
    opTempGroup->AddChild(TransferOwnership(ISectionViewSymbolNew::CreateModelViewElementShape(input, pModelView)));

    // 创建临时尺寸
    OwnerPtr<ITemporaryDimensionContext> opDimensionCtx = ITemporaryDimensionContext::CreateLinearContext(startPt, endPt, true, true, coordWorkPlane);
    opTempGroup->AddChild(ITemporaryDimensionManager::Get()->GenerateTemporaryDimensionGraphics(opDimensionCtx.get()));

    return opTempGroup;
}

ElementId CmdCreateSectionViewNew::OnLineFinished(IUiView* pCurrentView, const Vector3d& startPt, const Vector3d& endPt, const ISnap* pStartSnap, const ISnap* pEndSnap)
{
    DBG_WARN_AND_RETURN_UNLESS(startPt != endPt, ElementId::InvalidID, L"起终点不能相同",L"GDMPLab",L"2024-03-30");
    DBG_WARN_AND_RETURN_UNLESS(pCurrentView, ElementId::InvalidID, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
    IModelView* pModelView = pCurrentView->GetModelView();
    DBG_WARN_AND_RETURN_UNLESS(pModelView, ElementId::InvalidID, L"pModelView为空",L"GDMPLab",L"2024-03-30");
    IDocument* pDoc = UiDocumentViewUtils::GetCurrentDocument();
    DBG_WARN_AND_RETURN_UNLESS(pDoc, ElementId::InvalidID, L"pDoc为空",L"GDMPLab",L"2024-03-30");

    // 剖面视图的规则：
    // 1、用户绘制的两点形成的矢量和垂直屏幕的矢量来确定剖面图的剖切面
    // 2、相机的竖直方向确定原则：步骤1形成的面的法向和 + Z&-Z轴所围成的面和剖面之间的交线作为相机的垂直方向&负垂直方向
    // 3、当观察方向与z轴平行时，以起点到终点轴为右方向
    GbmpSectionViewInput input;
    {
        std::wstring viewName;
        bool bOk = GbmpModelViewUtil::GetNextModelViewName(pDoc, SectionViewType, BASE_NAME_SECTION_VIEW, L" ", viewName);
        DBG_WARN_AND_RETURN_UNLESS(bOk, ElementId::InvalidID, L"GetNextModelViewName失败",L"GDMPLab",L"2024-03-30");

        Coordinate3d coordWorkPlane = GbmpModelViewUtil::GetWorkPlaneCoordinate(pModelView);
        Vector3d origin, viewDirection, upDirection, rightDirection;
        GetSectionViewDirection(coordWorkPlane.Z(), startPt, endPt, origin, viewDirection, rightDirection, upDirection);
        Vector3d lineDir = endPt - startPt;
        double drawLineLength = lineDir.Normalize();

        input.Document = pDoc;
        input.Name = viewName;
        input.Origin = origin;
        input.ViewDirection = viewDirection;
        input.RightDirection = rightDirection;
        input.UpDirection = upDirection;
        if (Vector3dUtils::IsEqual(rightDirection, lineDir))   // 如果绘制的方向就是视图右方向，说明drawLineLength就是剖面的宽度
        {
            input.RectViewportWidth = drawLineLength;
            input.RectViewportHeight = 30000;
        }
        else        // 如果绘制的方向不是视图右方向，此时drawLineWidth是剖面的高度
        {
            input.RectViewportWidth = 30000;
            input.RectViewportHeight = drawLineLength;
        }
        input.BottomOffset = 30000;

        std::vector<const ILevel*> storeyLevels = LevelUtils::GetStoreyLevelsFromLowToHigh(pDoc);
        DBG_WARN_AND_RETURN_UNLESS(!storeyLevels.empty(), ElementId::InvalidID, L"storeyLevels 为空",L"GDMPLab",L"2024-03-30");

        input.Level = storeyLevels.front();
    }

    OwnerPtr<IUserTransaction> opTransaction = IUserTransaction::Create(pDoc, GBMP_TR(L"创建剖面视图"), true);
    DBG_WARN_AND_RETURN_UNLESS(opTransaction != nullptr, ElementId::InvalidID, L"事务创建不成功",L"GDMPLab",L"2024-03-30");

    IGbmpSectionView* pNewSectionView = IGbmpSectionView::Create(input);
    DBG_WARN_AND_RETURN_UNLESS(pNewSectionView, ElementId::InvalidID, L"pNewSectionView为空",L"GDMPLab",L"2024-03-30");
    opTransaction->Commit();

    // 选中刚刚绘制的剖面，显示其范围控制夹点
    UiDocumentViewUtils::GetCurrentUiDocument()->UpdateView();
    OwnerPtr<IGraphicsNodeReference> opGnodeRef = IGraphicsNodeReference::CreateGraphicsElementShapeReference(pNewSectionView->GetViewSymbolId());
    ISelection::Get()->Clear(UiDocumentViewUtils::GetCurrentDocument());
    ISelection::Get()->AddGraphicsNodeReference(UiDocumentViewUtils::GetCurrentDocument(), *opGnodeRef);

    IHighlights::Get()->Clear();
    std::vector<OwnerPtr<IGraphicsNodeReference>> opGnodeRefs;
    opGnodeRefs.push_back(TransferOwnership(opGnodeRef));
    IHighlights::Get()->AddGraphicsNodeReferences(opGnodeRefs);

    ElementId newSectionViewId = pNewSectionView->GetElementId();
    return newSectionViewId;
}
